搭建 vite + vue3 + ts + pinia 项目框架

您所在的位置:网站首页 vant search focus 无效 搭建 vite + vue3 + ts + pinia 项目框架

搭建 vite + vue3 + ts + pinia 项目框架

2023-04-07 04:43| 来源: 网络整理| 查看: 265

一、创建项目 1. 安装vite npm i vite -g 复制代码 2. 创建项目 一步创建 # npm 6.x npm create vite@latest my-vue-app --template vue-ts ​ # npm 7+, extra double-dash is needed: npm create vite@latest my-vue-app -- --template vue-ts ​ # yarn yarn create vite my-vue-app --template vue-ts ​ # pnpm pnpm create vite my-vue-app --template vue-ts 复制代码 配置创建 npm init vue@latest 复制代码

img

如果安装依赖后运行 npm run dev 报以下错误

image-20230128111555707.png

解决方法: 更新node版本

nodejs.org/zh-cn/

二、项目基本配置 1. 项目icon

在 public目录 下,添加一个 favicon.icon 图片

2. 项目标题

在 index.html 文件的 title标签 中配置

3. 配置 tsconfig.json

能让 代码提示 变得更加友好

{  "compilerOptions": {    // 允许从没有设置默认导出的模块中默认导入。这并不影响代码的输出,仅为了类型检查。    "allowSyntheticDefaultImports": true,    // 解析非相对模块名的基准目录    "baseUrl": ".",    // 模块加载兼容模式,可以是呀import from语法导入commonJS模块    "esModuleInterop": true,    // 从 tslib 导入辅助工具函数(比如 __extends, __rest等)    "importHelpers": true,    // 指定生成哪个模块系统代码    "module": "esnext",    // 决定如何处理模块。    "moduleResolution": "node",    // 启用所有严格类型检查选项。    // 启用 --strict相当于启用 --noImplicitAny, --noImplicitThis, --alwaysStrict,    // --strictNullChecks和 --strictFunctionTypes和--strictPropertyInitialization。    "strict": true, "noImplicitAny": false, // 在表达式和声明上有隐含的 any类型时不报错 "noUnusedLocals": false, // 有未使用的变量时,不抛出错误 "noUnusedParameters": false, // 有未使用的参数时,不抛出错误    // 支持jsx语法    "jsx": "preserve",    // 生成相应的 .map文件。    "sourceMap": true,    // 忽略所有的声明文件( *.d.ts)的类型检查。    "skipLibCheck": true,    // 指定ECMAScript目标版本    "target": "esnext",    // 要包含的类型声明文件名列表    "types": [      "node"   ],    "typeRoots": [      "../node_modules/@types"   ],    // isolatedModules 设置为 true 时,如果某个 ts 文件中没有一个import or export 时,ts 则认为这个模块不是一个 ES Module 模块,它被认为是一个全局的脚本,    "isolatedModules": true,    // 模块名到基于 baseUrl的路径映射的列表。    "paths": {      "@/*": [        "src/*"     ]   },    "vueCompilerOptions": {      "experimentalDisableTemplateSupport": true //去掉volar下el标签红色波浪线问题   },    // 编译过程中需要引入的库文件的列表。    "lib": [      "ESNext",      "DOM",      "DOM.Iterable",      "ScriptHost"   ] },  // 解析的文件  "include": [    "env.d.ts",    "src/**/*",    "src/**/*.ts",    "src/**/*.d.ts",    "src/**/*.tsx",    "src/**/*.vue",    "src/*.js",    "src/**/*.jsx" ],  "exclude": [    "node_modules" ],  "references": [   {      "path": "./tsconfig.node.json"   } ] } 复制代码 4. 设置 .prettierrc.json 文件

eslint 配置格式化选项说明

 // 1.一行代码的最大字符数,默认是80(printWidth: )  printWidth: 80,  // 2.tab宽度为2空格(tabWidth: )  tabWidth: 2,  // 3.是否使用tab来缩进,我们使用空格(useTabs: )  useTabs: false,  // 4.结尾是否添加分号,false的情况下只会在一些导致ASI错误的其工况下在开头加分号,我选择无分号结尾的风格(semi: )  semi: false,  // 5.使用单引号(singleQuote: )  singleQuote: true,  // 6.object对象中key值是否加引号(quoteProps: "")as-needed只有在需求要的情况下加引号,consistent是有一个需要引号就统一加,preserve是保留用户输入的引号  quoteProps: 'as-needed',  // 7.在jsx文件中的引号需要单独设置(jsxSingleQuote: )  jsxSingleQuote: false,  // 8.尾部逗号设置,es5是尾部逗号兼容es5,none就是没有尾部逗号,all是指所有可能的情况,需要node8和es2017以上的环境。(trailingComma: "")  trailingComma: 'es5',  // 9.object对象里面的key和value值和括号间的空格(bracketSpacing: )  bracketSpacing: true,  // 10.jsx标签多行属性写法时,尖括号是否另起一行(jsxBracketSameLine: )  jsxBracketSameLine: false,  // 11.箭头函数单个参数的情况是否省略括号,默认always是总是带括号(arrowParens: "")  arrowParens: 'always',  // 12.range是format执行的范围,可以选执行一个文件的一部分,默认的设置是整个文件(rangeStart: rangeEnd: )  rangeStart: 0,  rangeEnd: Infinity, // 13.不需要写文件开头的 @prettier requirePragma: false, // 14.不需要自动在文件开头插入 @prettier insertPragma: false,  // 15.vue script和style标签中是否缩进,开启可能会破坏编辑器的代码折叠  vueIndentScriptAndStyle: false, // 16.使用默认的折行标准 proseWrap: 'preserve', // 17.根据显示样式决定 html 要不要折行 htmlWhitespaceSensitivity: 'css',  // 18.endOfLine: "" 行尾换行符,默认是lf,  endOfLine: 'lf',  // 19.embeddedLanguageFormatting: "off",默认是auto,控制被引号包裹的代码是否进行格式化  embeddedLanguageFormatting: 'off', 复制代码 {  "singleQuote": true,  "tabWidth": 4,  "semi": false, } 复制代码 5. .eslintrc.js 文件参考 // ESlint 检查配置 module.exports = { root: true, parserOptions: { ecmaVersion: 12, parser: '@typescript-eslint/parser', sourceType: 'module', ecmaFeatures: { jsx: true, }, }, env: { browser: true, node: true, es6: true, }, parser: 'vue-eslint-parser', extends: [ 'eslint:recommended', 'plugin:vue/vue3-recommended', 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', // eslint-config-prettier 的缩写 'prettier', ], // eslint-plugin-vue @typescript-eslint/eslint-plugin eslint-plugin-prettier的缩写 plugins: ['vue', '@typescript-eslint', 'prettier'], rules: { '@typescript-eslint/ban-ts-ignore': 'off', '@typescript-eslint/no-unused-vars': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/no-explicit-any': 'off', '@typescript-eslint/no-var-requires': 'off', '@typescript-eslint/no-empty-function': 'off', '@typescript-eslint/no-use-before-define': 'off', '@typescript-eslint/no-this-alias': 'off', '@typescript-eslint/ban-ts-comment': 'off', '@typescript-eslint/ban-types': 'off', '@typescript-eslint/no-non-null-assertion': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', 'no-var': 'error', 'prettier/prettier': 'error', // 禁止出现console 'no-console': 'off', // 禁用debugger 'no-debugger': 'warn', // 禁止出现重复的 case 标签 'no-duplicate-case': 'warn', // 禁止出现空语句块 'no-empty': 'warn', // 禁止不必要的括号 'no-extra-parens': 'off', // 禁止对 function 声明重新赋值 'no-func-assign': 'warn', // 禁止在 return、throw、continue 和 break 语句之后出现不可达代码 'no-unreachable': 'warn', // 强制所有控制语句使用一致的括号风格 curly: 'warn', // 要求 switch 语句中有 default 分支 'default-case': 'warn', // 强制尽可能地使用点号 'dot-notation': 'warn', // 要求使用 === 和 !== eqeqeq: 'warn', // 禁止 if 语句中 return 语句之后有 else 块 'no-else-return': 'warn', // 禁止出现空函数 'no-empty-function': 'warn', // 禁用不必要的嵌套块 'no-lone-blocks': 'warn', // 禁止使用多个空格 'no-multi-spaces': 'warn', // 禁止多次声明同一变量 'no-redeclare': 'warn', // 禁止在 return 语句中使用赋值语句 'no-return-assign': 'warn', // 禁用不必要的 return await 'no-return-await': 'warn', // 禁止自我赋值 'no-self-assign': 'warn', // 禁止自身比较 'no-self-compare': 'warn', // 禁止不必要的 catch 子句 'no-useless-catch': 'warn', // 禁止多余的 return 语句 'no-useless-return': 'warn', // 禁止变量声明与外层作用域的变量同名 'no-shadow': 'off', // 允许delete变量 'no-delete-var': 'off', // 强制数组方括号中使用一致的空格 'array-bracket-spacing': 'warn', // 强制在代码块中使用一致的大括号风格 'brace-style': 'warn', // 强制使用骆驼拼写法命名约定 camelcase: 'warn', // 强制使用一致的缩进 indent: 'off', // 强制在 JSX 属性中一致地使用双引号或单引号 // 'jsx-quotes': 'warn', // 强制可嵌套的块的最大深度4 'max-depth': 'warn', // 强制最大行数 300 // "max-lines": ["warn", { "max": 1200 }], // 强制函数最大代码行数 50 // 'max-lines-per-function': ['warn', { max: 70 }], // 强制函数块最多允许的的语句数量20 'max-statements': ['warn', 100], // 强制回调函数最大嵌套深度 'max-nested-callbacks': ['warn', 3], // 强制函数定义中最多允许的参数数量 'max-params': ['warn', 3], // 强制每一行中所允许的最大语句数量 'max-statements-per-line': ['warn', { max: 1 }], // 要求方法链中每个调用都有一个换行符 'newline-per-chained-call': ['warn', { ignoreChainWithDepth: 3 }], // 禁止 if 作为唯一的语句出现在 else 语句中 'no-lonely-if': 'warn', // 禁止空格和 tab 的混合缩进 'no-mixed-spaces-and-tabs': 'warn', // 禁止出现多行空行 'no-multiple-empty-lines': 'warn', // 禁止出现; semi: 'warn', // 强制在块之前使用一致的空格 'space-before-blocks': 'warn', // 强制在 function的左括号之前使用一致的空格 // 'space-before-function-paren': ['warn', 'never'], // 强制在圆括号内使用一致的空格 'space-in-parens': 'warn', // 要求操作符周围有空格 'space-infix-ops': 'warn', // 强制在一元操作符前后使用一致的空格 'space-unary-ops': 'warn', // 强制在注释中 // 或 /* 使用一致的空格 // "spaced-comment": "warn", // 强制在 switch 的冒号左右有空格 'switch-colon-spacing': 'warn', // 强制箭头函数的箭头前后使用一致的空格 'arrow-spacing': 'warn', 'prefer-const': 'warn', 'prefer-rest-params': 'warn', 'no-useless-escape': 'warn', 'no-irregular-whitespace': 'warn', 'no-prototype-builtins': 'warn', 'no-fallthrough': 'warn', 'no-extra-boolean-cast': 'warn', 'no-case-declarations': 'warn', 'no-async-promise-executor': 'warn', 'vue/multi-word-component-names': 'off', }, globals: { defineProps: 'readonly', defineEmits: 'readonly', defineExpose: 'readonly', withDefaults: 'readonly', }, }; 复制代码 6. 设置 vite.config.ts 文件

安装 gzip 和 mock 依赖

npm i vite-plugin-compression vite-plugin-mock -D 复制代码 import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import vueJsx from '@vitejs/plugin-vue-jsx' ​ import path from 'path' // gzip插件 import viteCompression from 'vite-plugin-compression' // mock插件 import { viteMockServe } from 'vite-plugin-mock' ​ const resolve = (dir) => path.resolve(__dirname, dir) ​ export default defineConfig({    base: './', //打包路径    publicDir: resolve('public'), //静态资源服务的文件夹    plugins: [        vue(),        vueJsx(),        // gzip压缩 生产环境生成 .gz 文件        viteCompression({            verbose: true,            disable: false,            threshold: 10240,            algorithm: 'gzip',            ext: '.gz',       }),        //mock        viteMockServe({            mockPath: './mocks', // 解析,路径可根据实际变动            localEnabled: true, // 此处可以手动设置为true,也可以根据官方文档格式       }),   ],    // 配置别名    resolve: {        alias: {            '@': resolve('src'),       },        // 导入时想要省略的扩展名列表        extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue'],   },    css: {        // css预处理器        preprocessorOptions: {            scss: {                additionalData:                    '@import "@/assets/styles/common.scss";@import "@/assets/styles/reset.scss";',           },       },   },    //启动服务配置    server: {        host: '0.0.0.0',        port: 8000,        open: true, // 自动在浏览器打开        proxy: {},   },    // 打包配置    build: {        //浏览器兼容性 "esnext"|"modules"        target: 'modules',        //指定输出路径        outDir: 'build',        //生成静态资源的存放路径        assetsDir: 'assets',        //启用/禁用 CSS 代码拆分        cssCodeSplit: true,        sourcemap: false,        assetsInlineLimit: 10240,        // 打包环境移除console.log, debugger        minify: 'terser',        terserOptions: {            compress: {                drop_console: true,                drop_debugger: true,           },       },        rollupOptions: {            input: {                main: resolve('index.html'),           },            output: {                entryFileNames: `js/[name]-[hash].js`,                chunkFileNames: `js/[name]-[hash].js`,                assetFileNames: `[ext]/[name]-[hash].[ext]`,           },       },   }, }) ​ 复制代码 三、项目目录结构划分 assets 存放 => 静态资源 css => 样式重置 img => 图片文件 font => 字体文件 components 存放 => 公共组件 hooks 存放 => 公共常用的hook mock 存放 => 模拟接口数据 router 存放 => 路由管理 service 存放 => 接口请求 stores 存放 => 状态管理 utils 存放 => 插件、第三方插件 views 存放 => 视图、页面 四、css 样式重置

自定义的css公共文件放置在assets中的css文件中即可

1. normalize.css

01 - 安装

npm i normalize.css 复制代码

02 - 引入

// 在 main.js 中引入 import 'normalize.css'; 复制代码 2. reset.css

01 - 代码

html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, font, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, caption {    margin: 0;    padding: 0;    border: 0;    outline: 0;    font-size: 100%;    vertical-align: baseline;    background: transparent; } ​ table, tbody, tfoot, thead, tr, th, td {    margin: 0;    padding: 0;    outline: 0;    font-size: 100%;    vertical-align: baseline;    background: transparent; } ​ button, input, textarea {    margin: 0;    padding: 0; } ​ /* form elements 表单元素 */ body, button, input, select, textarea {    font: normal 12px/1.5 '\5FAE\8F6F\96C5\9ED1', tahoma, arial; } ​ /*设置的字体,行高*/ h1, h2, h3, h4, h5, h6, th {    font-size: 100%;    font-weight: normal; } ​ /*重置标题*/ address, cite, dfn, var {    font-style: normal; } ​ /* 将斜体扶正 */ code, kbd, pre, samp {    font-family: 'courier new', courier, monospace; } ​ /* 统一等宽字体 */ small {    font-size: 12px; } ​ /* 小于 12px 的中文很难阅读,让 small 正常化 */ ul, ol {    list-style: none; } ​ /* 重置列表元素 */ button, input[type="submit"], input[type="button"] {    cursor: pointer; } ​ input[type="radio"], input[type="checkbox"], input[type="submit"], input[type="reset"] {    vertical-align: middle;    cursor: pointer;    border: none; } ​ /** 重置文本格式元素 **/ a {    text-decoration: none; } a:hover {    text-decoration: underline; } a:focus {    outline: 0; } sup {    vertical-align: text-top; } ​ /* 重置,减少对行高的影响 */ sub {    vertical-align: text-bottom; } ​ /** 重置表单元素 **/ legend {    color: #000; } ​ /* for ie6 */ fieldset, img {    border: 0; } ​ /* img 搭车:让链接里的 img 无边框 */ button, input, select, textarea {    background: transparent;    font-size: 100%;    outline: 0; } ​ /* 使得表单元素在 ie 下能继承字体大小 */ /* 注:optgroup 无法扶正 */ table {    border-collapse: collapse;    border-spacing: 0; } ​ td, th {    vertical-align: middle; } ​ /** 重置表格元素 **/ /* 重置 HTML5 元素 */ article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section, summary, time, mark, audio, video {    display: block;    margin: 0;    padding: 0; } ​ /*回复标签重置*/ blockquote, q {    quotes: none; } ​ blockquote:before, blockquote:after, q:before, q:after {    content: '';    display: none; } 复制代码

简版样式重置

// 按照网站自己的需求,提供公用的样式 * { box-sizing: border-box; } html { height: 100%; font-size: 14px; } body { height: 100%; color: #333; min-width: 1240px; font: 1em/1.4 'Microsoft Yahei', 'PingFang SC', 'Avenir', 'Segoe UI', 'Hiragino Sans GB', 'STHeiti', 'Microsoft Sans Serif', 'WenQuanYi Micro Hei', sans-serif; } ul, h1, h3, h4, p, dl, dd { padding: 0; margin: 0; } a { text-decoration: none; color: #333; outline: none; } i { font-style: normal; } input[type='text'], input[type='search'], input[type='password'], input[type='checkbox'] { padding: 0; outline: none; border: none; -webkit-appearance: none; &::placeholder { color: #ccc; } } img { max-width: 100%; max-height: 100%; vertical-align: middle; // background: #ebebeb; } ul { list-style: none; } #app { background: #f5f5f5; // 不能选中文字 user-select: none; } .container { width: 1240px; margin: 0 auto; position: relative; } // 一行省略 .ellipsis { white-space: nowrap; text-overflow: ellipsis; overflow: hidden; } // 二行省略 .ellipsis-2 { word-break: break-all; text-overflow: ellipsis; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden; } .fl { float: left; } .fr { float: right; } .clearfix:after { content: '.'; display: block; visibility: hidden; height: 0; line-height: 0; clear: both; } // 闪动画 .shan { &::after { content: ''; position: absolute; animation: shan 1.5s ease 0s infinite; top: 0; width: 30%; height: 100%; background: linear-gradient(to left, rgba(255, 255, 255, 0) 0, rgba(255, 255, 255, 0.3) 50%, rgba(255, 255, 255, 0) 100%); transform: skewX(-45deg); } } @keyframes shan { 0% { left: -100%; } 100% { left: 120%; } } // 离开淡出动画 .fade { &-leave { &-active { position: absolute; width: 100%; transition: opacity 0.5s 0.2s; z-index: 1; } &-to { opacity: 0; } } } // 1. 离开,透明度 1---->0 位移 0---->30 // 2. 进入,透明度 0---->1 位移 30---->0 // 执行顺序,先离开再进入 .pop { &-leave { &-from { opacity: 1; transform: none; } &-active { transition: all 0.5s; } &-to { opacity: 0; transform: translateX(20px); } } &-enter { &-from { opacity: 0; transform: translateX(20px); } &-active { transition: all 0.5s; } &-to { opacity: 1; transform: none; } } } 复制代码

02 - 引入

// 在 main.js 中引入 import './assets/css/reset.css'; 复制代码 3. common.css

01 - 代码

@use "sass:math"; // 背景色 $background-color-white: #ffffff; $vm_base: 1920; $vh_base: 1080; /* 计算vw */ @function vw($px) { @return math.div($px, $vw_base) * 100vw; } /* 计算vh */ @function vh($px) { @return math.div($px, $vh_base) * 100vh; } /** * 全局样式代码块 */ @mixin flex($type) { display: flex; /* 水平居中 */ @if $type ==1 { justify-content: center; } /* 垂直居中 */ @else if($type ==2) { align-items: center; } /* 水平垂直居中 */ @else if($type ==3) { justify-content: center; align-items: center; } /* 水平拉伸垂直居中 */ @else if($type ==4) { justify-content: space-between; align-items: center; } /* 水平拉伸换行 */ @else if($type ==5) { justify-content: space-between; flex-wrap: wrap; } /* 换行 */ @else if($type ==6) { flex-wrap: wrap; } } /* 滚动条容器 */ ::-webkit-scrollbar { width: 6px; height: 6px; } /* 滚动槽 */ ::-webkit-scrollbar-track { border-radius: 10px; } /* 滚动条滑块 */ ::-webkit-scrollbar-thumb { background-color: #313340; border-radius: 10px; -webkit-transition: all .2s ease-in-out; &:hover { background-color: #4c4e59; cursor: pointer; } } /* 单行文本溢出 */ .text-hidden-siggle { white-space:nowrap; overflow:hidden; text-overflow: ellipsis; } /* 多行文本溢出 */ .text-hidden-mul { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden; } 复制代码

02 - 引入

// 在 main.js 中引入 import './assets/css/common.css'; 复制代码 五、vue-router 路由配置

一步创建需要安装依赖、配置路由, 引入mian.ts, 配置创建则已自动生成

1. 安装 npm i vue-router 复制代码 2. 配置 // 1. 导入 import { createRouter, createWebHashHistory } from 'vue-router'; ​ // 2. 创建路由对象 const router = createRouter({    history: createWebHashHistory(),    routes: [       {            path: '/',            redirect: '/home'       },       {            path: '/home',            component: () => import('xxx/home.vue')       }   ] }); ​ // 3. 导出 export default router; 复制代码 3. 引入 // main.js import { createApp } from 'vue'; import App from './App.vue'; // 1. 导入 import router from './router'; import 'normalize.css'; import './assets/css/reset.css'; import './assets/css/common.css'; // 2. 使用 createApp(App).use(router).mount('#app'); 复制代码 4. 使用

设置路由映射组件的展示区域

复制代码

设置超链接

首页 首页 这是广州校区 复制代码

编程式跳转

router.push('home') router.push({path:'/child/${itemId}'}) router.push({ path:"/home", query:{ id:this.id} }) router.push({ name:'user', params:{userId: '123'}}) router.go() router.go(-1) router.back() 复制代码 六、pinia 状态管理

一步创建需要安装依赖、配置路由, 引入mian.ts, 配置创建则已自动生成

1. 安装 npm i pinia npm i pinia-plugin-persist // 持久化 复制代码 2. 引入 // main.js import { createApp } from 'vue'; import { createPinia } from "pinia"; ​ import App from './App.vue'; // 1. 导入 import router from './router'; ​ import 'normalize.css'; import './assets/css/reset.css'; import './assets/css/common.css'; ​ // 2. 使用 createApp(App).use(createPinia()).use(router).mount('#app'); 复制代码 3. 模块 // 1. 导入 import { defineStore } from 'pinia'; ​ // 2. 使用 const useDemoStore = defineStore('demoStore', {    state: () => ({        arrList: []   }),    actions: {},    getters: {}, // 所有数据持久化 // persist: { // enabled: true, // }, // 持久化存储插件其他配置 persist: { enabled: true, strategies: [ // 修改存储中使用的键名称,默认为当前 Store的 id // 修改为 sessionStorage,默认为 localStorage // 部分持久化状态的点符号路径数组,[]意味着没有状态被持久化(默认为undefined,持久化整个状态) { key: 'storekey', storage: sessionStorage, paths: ['routes', 'userInfo'] }, // routes 和 userInfo字段用sessionStorage存储 { key: 'storekey', storage: localStorage, paths: ['token'] }, // token字段用 localstorage存储 ], }, }); ​ // 3. 导出 export default useDemoStore; 复制代码 4. 使用 import { useStore } from '@/stores/counter' const store = useStore() const { counter, doubleCount } = storeToRefs(store) store.$reset() // 将状态 重置 到其初始值 store.counter++ store.$patch({ counter: store.counter + 1, }) store.$state = { counter: 666 } pinia.state.value = {} 复制代码 七、集成 Axios HTTP 工具 安装依赖 npm i axios 复制代码 请求配置

在 utils 目录下创建 request.ts 文件,配置好适合自己业务的请求拦截和响应拦截

// vite环境变量 // .env.development NODE_ENV=development VITE_BASE_URL='http://xxx' // .env.production NODE_ENV=production VITE_BASE_URL='http://xxx' 复制代码 import axios, { AxiosRequestConfig, Method } from 'axios'; import { ElLoading } from 'element-plus'; // 创建请求实例 const instance = axios.create({ baseURL: importa.env.VITE_BASE_URL, // 指定请求超时的毫秒数 timeout: 10000, // 表示跨域请求时是否需要使用凭证 withCredentials: false, }); // 设置请求头 instance.defaults.headers.post['Content-Type'] = 'application/json;'; instance.defaults.headers.put['Content-Type'] = 'application/x-www-form-urlencoded'; // instance.defaults.headers.put['Content-Type'] = 'application/json'; // 加载实例 let loadingInstance = null; // 开始loading const startLoading = () => { if (!loadingInstance) { loadingInstance = ElLoading.service({}); } }; // 结束loading const endLoading = () => { if (loadingInstance) { loadingInstance.close(); loadingInstance = null } }; // 取消重复请求 const pending = []; // 定义接口 interface PendingType { url?: string; method?: Method; params: any; data: any; cancel: any; } // 移除重复请求 const removePending = (config: AxiosRequestConfig) => { for (const key in pending) { const item: number = +key; const list: PendingType = pending[key]; // 当前请求在数组中存在时执行函数体 if (list.url === config.url && listhod === config.method && JSON.stringify(list.params) === JSON.stringify(config.params) && JSON.stringify(list.data) === JSON.stringify(config.data)) { // 执行取消操作 list.cancel('操作太频繁,请稍后再试'); // 从数组中移除记录 pending.splice(item, 1); } } }; // 请求拦截器(发起请求之前的拦截) instance.interceptors.request.use( (config): AxiosRequestConfig => { // 不同基地址请求 // if (config.requestBase === UserEnum.adminBaseUrl) { // config.baseURL = window.configList.adminBaseUrl; // } removePending(config); config.cancelToken = new axios.CancelToken(c => { pending.push({ url: config.url, method: config.method, params: config.params, data: config.data, cancel: c }); }); /** * 在这里一般会携带前台的参数发送给后台,比如下面这段代码: * const token = sessionStorage.getItem('token') * if (token) { * config.headers.Authorization = `Basic ${token}` * } */ return config; }, (error) => { return Promise.reject(error); }, ); // 响应拦截器(获取到响应时的拦截) instance.interceptors.response.use( (response) => { removePending(response.config); /** * 根据你的项目实际情况来对 response 和 error 做处理 * 这里对 response 和 error 不做任何处理,直接返回 */ return response; }, (error) => { return Promise.reject(error); }, ); interface ResType { code: number; data?: T; msg?: string; message?: string; err?: string; } interface IOptions { loading?: boolean; isFormUrlencoded?: boolean; requestBase?: string; // 修改基地址 } interface Http { post(url: string, data?: unknown, options?: IOptions): Promise; get(url: string, options?: IOptions): Promise; put(url: string, data?: unknown, options?: IOptions): Promise; upload(url: string, file?: unknown): Promise; _delete(url: string, options?: IOptions): Promise; } /** * 是否是x-www-form-urlencoded格式请求 * @param {} */ function isFormUrlencoded(url, params, options) { if (options?.isFormUrlencoded) { const list: any[] = []; for (const key in params) { if (params[key] !== null) { list.push(`${key}=${encodeURIComponent(params[key])}`); } } const newParams = list.join('&'); const newOptions = { ...options, headers: { ...options.headers, 'Content-Type': 'application/x-www-form-urlencoded', }, }; return { newUrl: url + '?' + newParams, newParams: {}, newOptions }; } return { newUrl: url, newParams: params, newOptions: options }; } // 导出常用函数 const http: Http = { post(url, data, options?) { return new Promise((resolve, reject) => { options?.loading && startLoading(); const { newUrl, newData, newOptions } = isFormUrlencoded(data, options); instance .post(newUrl, JSON.stringify(newData), newOptions) .then((res) => { options?.loading && endLoading(); resolve(res.data); }) .catch((err) => { options?.loading && endLoading(); reject(err.data); }); }); }, get(url, params, options?) { return new Promise((resolve, reject) => { options?.loading && startLoading(); instance .get(url, { params, ...options }) .then((res) => { options?.loading && endLoading(); resolve(res.data); }) .catch((err) => { options?.loading && endLoading(); reject(err.data); }); }); }, put(url, data, options?) { return new Promise((resolve, reject) => { options?.loading && startLoading(); const { newUrl, newData, newOptions } = isFormUrlencoded(data, options); instance .put(newUrl, newData, newOptions) .then((res) => { options?.loading && endLoading(); resolve(res.data); }) .catch((err) => { options?.loading && endLoading(); reject(err.data); }); }); }, upload(url, file) { return new Promise((resolve, reject) => { // options?.loading && startLoading() instance .post(url, file, { headers: { 'Content-Type': 'multipart/form-data' }, }) .then((res) => { // options?.loading && endLoading() resolve(res.data); }) .catch((err) => { // options?.loading && endLoading() reject(err.data); }); }); }, downFile(url) { const iframe = document.createElement('iframe'); iframe.style.display = 'none'; iframe.src = url; iframe.onload = function () { document.body.removeChild(iframe); }; document.body.appendChild(iframe); }, downBlob(url,fileName) { fetch(url, { method: 'get', }) .then((res) => res.blob()) .then((blob) => { const a = document.createElement('a'); // 获取 blob 本地文件连接 (blob 为纯二进制对象,不能够直接保存到磁盘上) const url = window.URL.createObjectURL( new Blob([blob], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }), ); a.href = url; //定义导出的文件名 a.download = `${fileName}.xls`; a.click(); window.URL.revokeObjectURL(url); }); }, _delete(url, options?) { return new Promise((resolve, reject) => { options?.loading && startLoading(); instance .delete(url, options) .then((res) => { options?.loading && endLoading(); resolve(res.data); }) .catch((err) => { options?.loading && endLoading(); reject(err.data); }); }); } } export default http; 复制代码

之后在 api 文件夹中以业务模型对接口进行拆分,举个例子,将所有跟用户相关接口封装在 DictionaryAPI 对象中。

import request from '@/service/request'; const typeUrl = '/sysDict'; interface IBusinessType { dataName: string dataType: string pid: string } interface IBusinessSubType { businessSubType: string businessType: string } interface InDictionaryAPI {     allOptions: () => Promise;     businessTypeList: (params: IBusinessType) => Promise; businessSubTypeList: (params: IBusinessSubType) => Promise; } /** * 查询字典列表 */ const DictionaryAPI: InDictionaryAPI = { /** * 获取所有下拉选项 * @param {object} params */ allOptions() { return Promise.allSettled([ this.businessTypeList({ dataName: '', dataType: '', pid: '1' }), this.businessSubTypeList({ businessSubType: '', businessType: '' }), ]); }, /** * 紧急程度/业务类型 * @param {object} params */ businessTypeList(params = {}) { return request.post(typeUrl + '/findList', params); }, /** * 获取业务子类 * @param {object} params */ businessSubTypeList(params = {}) { return request.post(typeUrl + '/getBusinessSubTypeGroup', params, { isFormUrlencoded: true, requestBase: 'AdminBashUrl }); }, }; export default DictionaryAPI; 复制代码

把每个业务模型独立成一个 js 文件,声明一个类通过其属性和方法来实现这个模型相关的数据获取,这样可以大大提升代码的可读性与可维护性。

模拟演示

在需要使用接口的地方,引入对应的业务模型文件,参考如下

import { DictionaryAPI } from '@/service/api/index'; import { ref } from 'vue' /** * 获取所有下拉选项 * @param {type} 参数 * @returns {type} 返回值 */ async function getAllOptions() { try { const res: any = await DictionaryAPI.allOptions(); // ... } catch (error) { console.log(error); } }, 复制代码 八、使用scss, 并定义全局scss变量

首先我们先安装sass(不用安装sass-loader):

npm i sass -D 复制代码

然后我们需要在vite.config.ts中配置css预处理器

export default defineConfig({    css: {        preprocessorOptions: {            scss: {                additionalData: '@import "@/assets/styles/global.scss";@import "@/assets/styles/reset.scss";',           },       }   } }) 复制代码

我们这里默认加载global.scss中的样式,那么我们就需要创建一个这样的文件:

// src/assets/style/global.scss $primary-color: #5878e2; // 主题色 复制代码

最后在main.ts中引入即可:

import "./assets/style/global.scss"; 复制代码

然后在组件中使用时,就可以直接使用:

import {GlobalStore} from '@/store' const global = GlobalStore();    {{global.token}} div {    color: $primary-color; // 主题色 } 复制代码

样式穿透 在 Vue3 中,改变了以往样式穿透的语法,如果继续使用 ::v-deep、/deep/、>>> 等语法的话,会出现一个警告,下面是新的语法:

/* 深度选择器 */ :deep(selector) {   /* ... */ } /* 插槽选择器 */ :slotted(selector) {   /* ... */ } /* 全局选择器 */ :global(selector) {   /* ... */ } 复制代码

scss常用语法

1. 嵌套 .page { .page-header{ .header-left {} } .page-content {} } 2. 变量 $color:red; /* 使用 */ .scss_content{ color:$color; } 3. 计算 @use "sass:math"; // 放在文件第一行 $vm_base: 1920; $vh_base: 1080; /* 计算vw */ @function vw($px) { @return math.div($px, $vw_base) * 100vw; } /* 计算vh */ @function vh($px) { @return math.div($px, $vh_base) * 100vh; } /* 使用 */ .head{ font-size:vh(100); } 4. 混合 @mixin flex($type) { display: flex; /* 水平居中 */ @if $type ==1 { justify-content: center; } /* 垂直居中 */ @else if($type ==2) { align-items: center; } /* 水平垂直居中 */ @else if($type ==3) { justify-content: center; align-items: center; } /* 水平拉伸垂直居中 */ @else if($type ==4) { justify-content: space-between; align-items: center; } /* 水平拉伸换行 */ @else if($type ==5) { justify-content: space-between; flex-wrap: wrap; } /* 换行 */ @else if($type ==6) { flex-wrap: wrap; } } /* 使用 */ @include flex(1); 复制代码


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3